#ifdef __EOS__
#include <rtthread.h>
#include "irq_numbers.h"
#include "clk.h"
#else
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/printk.h>
#include <linux/version.h>
#include <linux/of_platform.h>
#include <linux/clk.h>
#include <linux/pm_runtime.h>
#include <linux/pm_clock.h>
#endif

#include "ei_type.h"
#include "ei_common.h"
#include "ei_os.h"

#define LOMBO_VGSS_EXT_SCLK_NUM 2
#define LOMBO_VGSS_SCLK_NUM 2

#define EI_DEV_ROT "ei_rot"
#define EI_DEV_GDC "ei_gdc"
#define EI_DEV_G2D "ei_g2d"
#define EI_DEV_CQI "ei_cqi"

#define GDC_DEFAULT_CLK_RATE (300 * 1000 * 1000)

#ifdef __EOS__
struct ei_vgss_res {
    void *reg;
    void *regs_res;
    unsigned int reg_size;
};
#else
struct ei_vgss_res {
    void __iomem *reg;
    struct resource *regs_res;
    struct platform_device *pdev;
    unsigned int reg_size;
};
#endif

typedef struct lombo_vgss_clk_func {
    long(*get_rate)(void *);
    int(*set_rate)(void *, unsigned long);
    int(*clk_enable)(void *);
    void(*clk_disable)(void *);
} lombo_vgss_clk_func_s;

typedef struct lombo_vgss_pm_func {
    void(*pm_suspend)(void);
    void(*pm_resume)(void);
} lombo_vgss_pm_func_s;

struct ei_vgss_context {
    void *pstVgssEx;
    void *pstRotEx;
    void *pstGdcEx;
    void *pstG2dEx;
    void *pstCqiEx;
    struct ei_vgss_res *pstTopRes;
    struct ei_vgss_res *pstRotRes;
    struct ei_vgss_res *pstGdcRes;
    struct ei_vgss_res *pstG2dRes;
    struct ei_vgss_res *pstCqiRes;
};

struct vgss_adpll_config {
    u32 n; /* pll n factor, must be >= 10 */
    u32 tune0;
    u32 tune1;
};

struct lombo_vgss_sclk {
    void *ext_sclk;
    void *ext_psclk;

    unsigned long req_rate;
    unsigned long real_rate;
    int use_adpll;
    struct vgss_adpll_config adpll_cfg;

    struct os_mutex slock;
    int en_cnt;
};

extern int ei_vgss_init_helper(void **pstVgssEx);
extern int ei_vgss_exit_helper(void *pstVgssEx);

extern int lombo_vgss_get_top(void *pstVgssEx);
extern int lombo_vgss_put_top(void *pstVgssEx);

extern int lombo_vgss_probe_do(void *pstVgssEx,
    lombo_vgss_clk_func_s *clk_ops,
    void *reg, unsigned int reg_size);
extern int lombo_vgss_remove_do(void *pstVgssEx);

extern int lombo_vgss_set_sclk(void *pstVgssEx, int i, struct lombo_vgss_sclk sclk);

extern int lombo_vgss_parse_sclk_do(void *pstVgssEx,
    void *ahb_gate, void *ahb_reset, void *mem_axi_gate,
    void *ext_sclk[LOMBO_VGSS_EXT_SCLK_NUM],
    void *ext_psclk[LOMBO_VGSS_EXT_SCLK_NUM]);

extern int lombo_vgss_get_rot(void **pstRotEx);
extern int lombo_vgss_put_rot(void *pstRotEx);

extern int lombo_rot_probe_do(void *pstVgssEx, void *pstRotEx,
    void *regs_res, void *reg, unsigned int reg_size,
    unsigned int irq, unsigned int clk_rate, unsigned int sclk);
extern void lombo_rot_remove_do(void *pstRotEx);

extern int lombo_vgss_get_gdc(void **pstGdcEx);
extern int lombo_vgss_put_gdc(void *pstGdcEx);

extern int lombo_gdc_probe_do(void *pstVgssEx, void *pstGdcEx,
    void *regs_res, void *reg, unsigned int reg_size,
    unsigned int irq, unsigned int clk_rate, unsigned int sclk);
extern void lombo_gdc_remove_do(void *pstGdcEx);

extern int lombo_vgss_get_g2d(void **pstG2dEx);
extern int lombo_vgss_put_g2d(void *pstG2dEx);

extern int lombo_g2d_probe_do(void *pstVgssEx, void *pstG2dEx,
    void *regs_res, void *reg, unsigned int reg_size,
    unsigned int irq, unsigned int clk_rate, unsigned int sclk);
extern void lombo_g2d_remove_do(void *pstG2dEx);

extern int lombo_vgss_get_cqi(void **pstCqiEx);

extern int lombo_vgss_put_cqi(void *pstCqiEx);

extern int lombo_cqi_probe_do(void *pstVgssEx, void *pstG2dEx, void *pstCqiEx,
    void *regs_res, void *reg, unsigned int reg_size,
    unsigned int irq, unsigned int clk_rate, unsigned int sclk);
extern void lombo_cqi_remove_do(void *pstCqiEx);

extern int ei_vgss_gate_enable(unsigned int mode);
extern int ei_vgss_gate_disable(unsigned int mode);

extern int ei_vgss_reset_enable(unsigned int mode);
extern int ei_vgss_reset_disable(unsigned int mode);

extern int lombo_rot_set_pm_fun(void *pstRotEx,
    lombo_vgss_pm_func_s *pm_func);
extern int lombo_gdc_set_pm_fun(void *pstGdcEx,
    lombo_vgss_pm_func_s *pm_func);
extern int lombo_g2d_set_pm_fun(void *pstG2dEx,
    lombo_vgss_pm_func_s *pm_func);
extern int lombo_cqi_set_pm_fun(void *pstCqiEx,
    lombo_vgss_pm_func_s *pm_func);
extern int lombo_vgss_set_pm_fun(void *pstVgssEx,
    lombo_vgss_pm_func_s *pm_func);

extern int ei_vgss_adpll_enable(void *pstVgssEx, unsigned int sclk_id);
extern int ei_vgss_adpll_disable(void);

extern int ei_vgss_top_and_sub_resume(void *pstVgssEx);

#ifdef __EOS__

static long ei_vgss_clk_get_rate(void *clk)
{
    return clk_get_rate((clk_handle_t)clk);
}

static int ei_vgss_clk_set_rate(void *clk, unsigned long rate)
{
    return clk_set_rate((clk_handle_t)clk, rate);
}

static int ei_vgss_clk_prepare_enable(void *clk)
{
    return clk_enable((clk_handle_t)clk);
}

static void ei_vgss_clk_disable_unprepare(void *clk)
{
    return clk_disable((clk_handle_t)clk);
}

static int lombo_vgss_parse_sclk(void *pstVgssEx)
{
    int i;
    void *ahb_gate = NULL;
    void *ahb_reset = NULL;
    void *mem_axi_gate = NULL;
    void *ext_sclk[LOMBO_VGSS_EXT_SCLK_NUM] = {0};
    void *ext_psclk[LOMBO_VGSS_EXT_SCLK_NUM] = {0};
    struct lombo_vgss_sclk top_sclk[LOMBO_VGSS_SCLK_NUM] = {0};

    /* parse bus clks */
    ahb_gate = (void *)clk_get("ahb_vgss_gate");
    if (!ahb_gate) {
        os_printk("failed to get vgss ahb_gate\n");
        return -ENODEV;
    }
    ahb_reset = (void *)clk_get("ahb_vgss_reset");
    if (!ahb_reset) {
        os_printk("failed to get vgss ahb_reset\n");
        return -ENODEV;
    }
    mem_axi_gate = (void *)clk_get("maxi_vgss_gate");
    if (!mem_axi_gate) {
        os_printk("failed to get vgss mem_axi_gate\n");
        return -ENODEV;
    }

    /* parse ext sclk & psclk */
    for (i = 0; i < LOMBO_VGSS_EXT_SCLK_NUM; i++) {
        clk_handle_t clk, pclk;

        if (i == 0) {
            clk = clk_get("vgss_clk");
        } else {
            clk = clk_get("vgss_osc24m");
        }
        if (!clk) {
            os_printk("failed to get clk[%d]\n", i);
            continue;
        }
        ext_sclk[i] = (void *)clk;

        if (i == 0) {
            pclk = clk_get("perh0_pll_div2");
        } else {
            pclk = clk_get("osc24m");
        }
        if (pclk) {
            clk_set_parent(clk, pclk);
            ext_psclk[i] = (void *)pclk;
        } else {
            os_printk("failed to get pclk[%d]\n", i);
        }

        /* os_printk("has clk%d\n", i); */
    }

    lombo_vgss_parse_sclk_do(pstVgssEx,
        ahb_gate, ahb_reset, mem_axi_gate,
        ext_sclk, ext_psclk);

    /* create inner sclk */
    for (i = 0; i < LOMBO_VGSS_SCLK_NUM; i++) {
        struct lombo_vgss_sclk *sclk = &(top_sclk[i]);

        sclk->ext_sclk = ext_sclk[i];
        sclk->ext_psclk = ext_psclk[i];
        if (!sclk->ext_sclk) {
            os_printk("not ext_sclk for sclk%d\n", i);
            return -EINVAL;
        }

        if (!sclk->ext_sclk) {
            os_printk("not ext_sclk for sclk%d\n", i);
            return -EINVAL;
        }

        sclk->use_adpll = 0;

#if 0
        os_printk("%s:req_rate=%lu, adpll=%d.",
            name, sclk->req_rate, sclk->use_adpll);
        os_printk("adpll_tune[0x%x,0x%x]\n",
            sclk->adpll_cfg.tune0, sclk->adpll_cfg.tune1);
#endif
        lombo_vgss_set_sclk(pstVgssEx, i, top_sclk[i]);
    }

    return 0;
}

#define BASE_VGSS 0x01900000
#define VGSS_REG_NUM 64
#define VGSS_REG_SIZE (VGSS_REG_NUM * 4)

int lombo_vgss_probe(struct ei_vgss_context *pstEiVgssCtx)
{
    struct ei_vgss_res *resCtx;
    int ret;
    lombo_vgss_clk_func_s clk_ops;

    os_printk("%s:%d\n", __FUNCTION__, __LINE__);
    resCtx = os_kzalloc(sizeof(struct ei_vgss_res), os_gfp_kernel);
    if (!resCtx) {
        os_printk("Kernel: alloc for resCtx fail\n");
        return -ENOMEM;
    }

    resCtx->reg = (void *)(BASE_VGSS);
    resCtx->reg_size = VGSS_REG_SIZE;

    clk_ops.get_rate = ei_vgss_clk_get_rate;
    clk_ops.set_rate = ei_vgss_clk_set_rate;
    clk_ops.clk_enable = ei_vgss_clk_prepare_enable;
    clk_ops.clk_disable = ei_vgss_clk_disable_unprepare;

    ret = lombo_vgss_get_top(pstEiVgssCtx->pstVgssEx);
    if (ret) {
        os_kfree(resCtx);
        os_printk("lombo_vgss_get_top fail\n");
        return ret;
    }

    ret = lombo_vgss_parse_sclk(pstEiVgssCtx->pstVgssEx);
    if (ret) {
        os_printk("lombo_vgss_parse_sclk fail\n");
        return ret;
    }

    ret = lombo_vgss_probe_do(pstEiVgssCtx->pstVgssEx, &clk_ops,
            (void *)resCtx->reg, resCtx->reg_size);
    if (ret) {
        os_printk("lombo_vgss_probe_do fail\n");
        return ret;
    }

    pstEiVgssCtx->pstTopRes = resCtx;

    return 0;
}

#define BASE_ROT 0x01930000
#define VGSS_ROT_REG_SIZE 2048

int lombo_rot_probe(struct ei_vgss_context *pstEiVgssCtx)
{
    struct ei_vgss_res *resCtx;
    int ret = 0;
    unsigned int irq, clk_rate = 0, sclk = 0;

    os_printk("%s:%d\n", __FUNCTION__, __LINE__);
    resCtx = os_kzalloc(sizeof(struct ei_vgss_res), os_gfp_kernel);
    if (!resCtx) {
        os_printk("Kernel: alloc for resCtx fail\n");
        return -ENOMEM;
    }

    resCtx->reg = (void *)(BASE_ROT);
    resCtx->reg_size = VGSS_ROT_REG_SIZE;
    irq = INT_ROT;
    clk_rate = 198000000;
    sclk = 0;

    ret = lombo_vgss_get_rot(&pstEiVgssCtx->pstRotEx);
    if (ret) {
        os_kfree(resCtx);
        os_printk("lombo_vgss_get_rot fail\n");
        return ret;
    }

    ret = lombo_rot_probe_do(pstEiVgssCtx->pstVgssEx, pstEiVgssCtx->pstRotEx,
            (void *)resCtx->regs_res, (void *)resCtx->reg, resCtx->reg_size,
            irq, clk_rate, sclk);
    if (ret) {
        os_printk("lombo_rot_probe_do fail\n");
        return ret;
    }

    pstEiVgssCtx->pstRotRes = resCtx;

    return ret;
}

#define BASE_GDC 0x01950000
#define VGSS_GDC_REG_SIZE 256

int lombo_gdc_probe(struct ei_vgss_context *pstEiVgssCtx)
{
    struct ei_vgss_res *resCtx;
    int ret = 0;
    unsigned int irq, clk_rate = 0, sclk = 0;

    os_printk("%s:%d\n", __FUNCTION__, __LINE__);
    resCtx = os_kzalloc(sizeof(struct ei_vgss_res), os_gfp_kernel);
    if (!resCtx) {
        os_printk("Kernel: alloc for resCtx fail\n");
        return -ENOMEM;
    }

    resCtx->reg = (void *)(BASE_GDC);
    resCtx->reg_size = VGSS_GDC_REG_SIZE;
    irq = INT_GDC_INT;
    clk_rate = GDC_DEFAULT_CLK_RATE;
    sclk = 0;

    ret = lombo_vgss_get_gdc(&pstEiVgssCtx->pstGdcEx);
    if (ret) {
        os_kfree(resCtx);
        os_printk("lombo_vgss_get_gdc fail\n");
        return ret;
    }

    ret = lombo_gdc_probe_do(pstEiVgssCtx->pstVgssEx, pstEiVgssCtx->pstGdcEx,
            (void *)resCtx->regs_res, (void *)resCtx->reg, resCtx->reg_size,
            irq, clk_rate, sclk);
    if (ret) {
        os_printk("lombo_gdc_probe_do fail\n");
        return ret;
    }

    pstEiVgssCtx->pstGdcRes = resCtx;

    return ret;
}

#define BASE_G2D 0x01920000
#define VGSS_G2D_REG_SIZE 0x10000

int lombo_g2d_probe(struct ei_vgss_context *pstEiVgssCtx)
{
    struct ei_vgss_res *resCtx;
    int ret = 0;
    unsigned int irq, clk_rate = 0, sclk = 0;

    os_printk("%s:%d\n", __FUNCTION__, __LINE__);
    resCtx = os_kzalloc(sizeof(struct ei_vgss_res), os_gfp_kernel);
    if (!resCtx) {
        os_printk("Kernel: alloc for resCtx fail\n");
        return -ENOMEM;
    }

    resCtx->reg = (void *)(BASE_G2D);
    resCtx->reg_size = VGSS_G2D_REG_SIZE;
    irq = INT_G2D;
    clk_rate = 198000000;
    sclk = 0;

    ret = lombo_vgss_get_g2d(&pstEiVgssCtx->pstG2dEx);
    if (ret) {
        os_kfree(resCtx);
        os_printk("lombo_vgss_get_g2d fail\n");
        return ret;
    }

    ret = lombo_g2d_probe_do(pstEiVgssCtx->pstVgssEx, pstEiVgssCtx->pstG2dEx,
            (void *)resCtx->regs_res, (void *)resCtx->reg, resCtx->reg_size,
            irq, clk_rate, sclk);
    if (ret) {
        os_printk("lombo_g2d_probe_do fail\n");
        return ret;
    }

    pstEiVgssCtx->pstG2dRes = resCtx;

    return ret;
}

#define BASE_CQI 0x019f0000
#define VGSS_CQI_REG_SIZE 0x10000

int lombo_g2d_cqi_probe(struct ei_vgss_context *pstEiVgssCtx)
{
    struct ei_vgss_res *resCtx;
    int ret = 0;
    unsigned int irq, clk_rate = 0, sclk = 0;

    os_printk("%s:%d\n", __FUNCTION__, __LINE__);
    resCtx = os_kzalloc(sizeof(struct ei_vgss_res), os_gfp_kernel);
    if (!resCtx) {
        os_printk("Kernel: alloc for resCtx fail\n");
        return -ENOMEM;
    }

    resCtx->reg = (void *)(BASE_CQI);
    resCtx->reg_size = VGSS_CQI_REG_SIZE;
    irq = INT_CQI;
    clk_rate = 198000000;
    sclk = 0;

    ret = lombo_vgss_get_cqi(&pstEiVgssCtx->pstCqiEx);
    if (ret) {
        os_kfree(resCtx);
        os_printk("lombo_vgss_get_cqi fail\n");
        return ret;
    }

    ret = lombo_cqi_probe_do(pstEiVgssCtx->pstVgssEx,
            pstEiVgssCtx->pstG2dEx, pstEiVgssCtx->pstCqiEx,
            (void *)resCtx->regs_res, (void *)resCtx->reg, resCtx->reg_size,
            irq, clk_rate, sclk);
    if (ret) {
        os_printk("lombo_cqi_probe_do fail\n");
        return ret;
    }

    pstEiVgssCtx->pstCqiRes = resCtx;

    return 0;
}

int vgss_driver_init(void)
{
    int ret;
    struct ei_vgss_context *pstEiVgssCtx;

    pstEiVgssCtx = os_kzalloc(sizeof(*pstEiVgssCtx), os_gfp_kernel);
    if (!pstEiVgssCtx) {
        os_printk("Kernel: alloc for pstEiVgssCtx fail\n");
        return -ENOMEM;
    }

    ret = ei_vgss_init_helper(&pstEiVgssCtx->pstVgssEx);
    if (ret) {
        os_printk("ei_vgss_init_helper fail\n");
        return ret;
    }

    ret = lombo_vgss_probe(pstEiVgssCtx);
    if (ret) {
        os_printk("lombo_vgss_probe fail\n");
    }

    ret = lombo_rot_probe(pstEiVgssCtx);
    if (ret) {
        os_printk("lombo_rot_probe fail\n");
    }

    ret = lombo_gdc_probe(pstEiVgssCtx);
    if (ret) {
        os_printk("lombo_gdc_probe fail\n");
    }

    ret = lombo_g2d_probe(pstEiVgssCtx);
    if (ret) {
        os_printk("lombo_g2d_probe fail\n");
    }

    ret = lombo_g2d_cqi_probe(pstEiVgssCtx);
    if (ret) {
        os_printk("lombo_g2d_cqi_probe fail\n");
    }

    return 0;
}


#else

static int g_ext_sclk_use_cnt[LOMBO_VGSS_EXT_SCLK_NUM] = {};

#ifdef CONFIG_PM
static void *g_ahb_gate = NULL;
static void *g_ahb_reset = NULL;
static void *g_mem_axi_gate = NULL;
static void *g_ext_sclk[LOMBO_VGSS_EXT_SCLK_NUM] = {};
static int g_ext_sclk1_enable_cnt = 0;
static void *g_pstVgssEx = NULL;

static spinlock_t vgss_lock;

static struct platform_device *g_rot_pdev;
static struct platform_device *g_gdc_pdev;
static struct platform_device *g_g2d_pdev;
static struct platform_device *g_cqi_pdev;

enum  {
    VGSS_MOD_G2D,
    VGSS_MOD_ROT,
    VGSS_MOD_DIT,
    VGSS_MOD_CQI,
    VGSS_MOD_GDC,
    VGSS_MOD_ABT,
    VGSS_MOD_SUB_MAX,
    VGSS_MOD_ADPLL0 = 0xc,
    VGSS_MOD_TIMER = 0xd,
    VGSS_MOD_TOP = 0xe,
} ;
#endif

static long ei_vgss_clk_get_rate(void *clk)
{
    return clk_get_rate((struct clk *)clk);
}

static int ei_vgss_clk_set_rate(void *clk, unsigned long rate)
{
    return clk_set_rate((struct clk *)clk, rate);
}

static int ei_vgss_clk_prepare_enable(void *clk)
{
    return clk_prepare_enable((struct clk *)clk);
}

static void ei_vgss_clk_disable_unprepare(void *clk)
{
    clk_disable_unprepare((struct clk *)clk);
}

#ifdef CONFIG_PM
static int lombo_rot_runtime_suspend(struct device *dev)
{
    //os_printk("%s:%d\n", __FUNCTION__, __LINE__);
    /* disable the gating of rot */
    spin_lock(&vgss_lock);
    ei_vgss_gate_disable(VGSS_MOD_ROT);
    spin_unlock(&vgss_lock);

    return 0;
}

static int lombo_rot_runtime_resume(struct device *dev)
{
    //os_printk("%s:%d\n", __FUNCTION__, __LINE__);
    /* enable the gating of rot */
    spin_lock(&vgss_lock);
    ei_vgss_gate_enable(VGSS_MOD_ROT);
    spin_unlock(&vgss_lock);

    return 0;
}

#ifdef CONFIG_PM_SLEEP
static int lombo_rot_suspend(struct device *dev)
{
    //os_printk("%s:%d\n", __FUNCTION__, __LINE__);
    return 0;
}

static int lombo_rot_resume(struct device *dev)
{
    //os_printk("%s:%d\n", __FUNCTION__, __LINE__);
    ei_vgss_reset_disable(VGSS_MOD_ROT);
    ei_vgss_reset_enable(VGSS_MOD_ROT);

    return 0;
}
#endif

static int ei_vgss_rot_probe(struct platform_device *pdev)
{
    //os_printk("%s:%d\n", __FUNCTION__, __LINE__);
    pm_runtime_set_active(&pdev->dev);
    /* enable the pm_runtime */
    pm_runtime_enable(&pdev->dev);
    g_rot_pdev = pdev;

    return 0;
}

static int ei_vgss_rot_remove(struct platform_device *pdev)
{
    //os_printk("%s:%d\n", __FUNCTION__, __LINE__);
    pm_runtime_disable(&pdev->dev);

    return 0;
}

static const struct dev_pm_ops pm_rot_runtime_ops = {
    SET_RUNTIME_PM_OPS(lombo_rot_runtime_suspend,
        lombo_rot_runtime_resume, NULL)
#ifdef CONFIG_PM_SLEEP
    SET_SYSTEM_SLEEP_PM_OPS(lombo_rot_suspend,
        lombo_rot_resume)
#endif
};

static const struct of_device_id ei_vgss_rot_math[] = {
    { .compatible = "ei, ei-vgss-rot", },
    {}
};
MODULE_DEVICE_TABLE(of, ei_vgss_rot_math);

static struct platform_driver rot_pm_driver = {
    .driver = {
        .name = "ei_rot",
        .owner = THIS_MODULE,
        .of_match_table = ei_vgss_rot_math,
        .pm = &pm_rot_runtime_ops,
    },
    .probe = ei_vgss_rot_probe,
    .remove = ei_vgss_rot_remove,
};

void ei_rot_pm_suspend(void)
{
    //os_printk("%s:%d\n", __FUNCTION__, __LINE__);
    /* this disable the clk firstly, then close the power */
    pm_runtime_put_sync(&g_rot_pdev->dev);
}

void ei_rot_pm_resume(void)
{
    //os_printk("%s:%d\n", __FUNCTION__, __LINE__);
    /* this can open the power then enable the clk */
    pm_runtime_get_sync(&g_rot_pdev->dev);
}

void lombo_rot_pm_init(struct platform_device *pdev, void *pstRotEx)
{
    lombo_vgss_pm_func_s pm_func;

    platform_driver_register(&rot_pm_driver);
    pm_func.pm_suspend = ei_rot_pm_suspend;
    pm_func.pm_resume = ei_rot_pm_resume;
    lombo_rot_set_pm_fun(pstRotEx, &pm_func);
}

void lombo_rot_pm_exit(struct platform_device *pdev)
{
    platform_driver_unregister(&rot_pm_driver);
}
#endif

int lombo_rot_probe(struct platform_device *pdev,
    struct ei_vgss_context *pstEiVgssCtx)
{
    struct ei_vgss_res *resCtx;
    struct device *dev = &pdev->dev;
    struct device_node *np = dev->of_node;
    struct resource *res;
    int ret = 0;
    unsigned int irq, clk_rate = 0, sclk = 0;

    /* os_printk("%s:%d\n", __FUNCTION__, __LINE__); */
    ret = lombo_vgss_get_rot(&pstEiVgssCtx->pstRotEx);
    if (ret) {
        os_printk("lombo_vgss_get_rot fail\n");
        return ret;
    }
#ifdef CONFIG_PM
    lombo_rot_pm_init(pdev, pstEiVgssCtx->pstRotEx);
#endif

    resCtx = os_kzalloc(sizeof(struct ei_vgss_res), os_gfp_kernel);
    if (!resCtx) {
        os_printk("Kernel: alloc for resCtx fail\n");
        return -ENOMEM;
    }
    resCtx->pdev = pdev;

    /* parse register */
    resCtx->regs_res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    resCtx->reg = devm_ioremap_resource(dev, resCtx->regs_res);
    if (IS_ERR(resCtx->reg)) {
        os_printk("failed to get reg for rot\n");
        return PTR_ERR(resCtx->reg);
    }

    resCtx->reg_size = resource_size(resCtx->regs_res);

    /* parse irq */
    res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
    if (res == NULL) {
        os_printk("get interrupt resource failed.\n");
        return -ENXIO;
    }
    irq = res->start;

    if (of_property_read_u32(np, "clk_rate", &clk_rate)) {
        os_printk("failed to of get rot clk_rate\n");
    }

    of_property_read_u32(np, "sclk", &sclk);
    if (sclk)
        g_ext_sclk_use_cnt[1]++;
    else
        g_ext_sclk_use_cnt[0]++;

    dev_set_drvdata(dev, pstEiVgssCtx->pstRotEx);

    ret = lombo_rot_probe_do(pstEiVgssCtx->pstVgssEx, pstEiVgssCtx->pstRotEx,
            (void *)resCtx->regs_res, (void *)resCtx->reg, resCtx->reg_size,
            irq, clk_rate, sclk);
    if (ret) {
        os_printk("lombo_rot_probe_do fail\n");
        return ret;
    }

    pstEiVgssCtx->pstRotRes = resCtx;

    return ret;
}

void lombo_rot_remove(struct ei_vgss_context *pstEiVgssCtx)
{
    struct ei_vgss_res *resCtx = (struct ei_vgss_res *)pstEiVgssCtx->pstRotRes;
    struct device *dev = &resCtx->pdev->dev;

    lombo_rot_remove_do(pstEiVgssCtx->pstRotEx);
    devm_release_mem_region(dev, resCtx->regs_res->start, resCtx->reg_size);
#ifdef CONFIG_PM
    lombo_rot_pm_exit(resCtx->pdev);
#endif
    os_kfree(resCtx);
    lombo_vgss_put_rot(pstEiVgssCtx->pstRotEx);
}

#ifdef CONFIG_PM
static int lombo_gdc_runtime_suspend(struct device *dev)
{
    //os_printk("%s:%d\n", __FUNCTION__, __LINE__);
    /* disable the gating of gdc */
    spin_lock(&vgss_lock);
    ei_vgss_reset_disable(VGSS_MOD_GDC);
    ei_vgss_gate_disable(VGSS_MOD_GDC);
    spin_unlock(&vgss_lock);

    return 0;
}

static int lombo_gdc_runtime_resume(struct device *dev)
{
    //os_printk("%s:%d\n", __FUNCTION__, __LINE__);
    /* enable the gating of gdc */
    spin_lock(&vgss_lock);
    ei_vgss_gate_enable(VGSS_MOD_GDC);
    ei_vgss_reset_enable(VGSS_MOD_GDC);
    spin_unlock(&vgss_lock);

    return 0;
}

#ifdef CONFIG_PM_SLEEP
static int lombo_gdc_suspend(struct device *dev)
{
    //os_printk("%s:%d\n", __FUNCTION__, __LINE__);
    return 0;
}

static int lombo_gdc_resume(struct device *dev)
{
    //os_printk("%s:%d\n", __FUNCTION__, __LINE__);
    ei_vgss_reset_disable(VGSS_MOD_GDC);
    ei_vgss_reset_enable(VGSS_MOD_GDC);

    return 0;
}
#endif

static int ei_vgss_gdc_probe(struct platform_device *pdev)
{
    //os_printk("%s:%d\n", __FUNCTION__, __LINE__);
    pm_runtime_set_active(&pdev->dev);
    /* enable the pm_runtime */
    pm_runtime_enable(&pdev->dev);
    g_gdc_pdev = pdev;

    return 0;
}

static int ei_vgss_gdc_remove(struct platform_device *pdev)
{
    //os_printk("%s:%d\n", __FUNCTION__, __LINE__);
    pm_runtime_disable(&pdev->dev);

    return 0;
}

static const struct dev_pm_ops pm_gdc_runtime_ops = {
    SET_RUNTIME_PM_OPS(lombo_gdc_runtime_suspend,
        lombo_gdc_runtime_resume, NULL)
#ifdef CONFIG_PM_SLEEP
    SET_SYSTEM_SLEEP_PM_OPS(lombo_gdc_suspend,
        lombo_gdc_resume)
#endif
};

static const struct of_device_id ei_vgss_gdc_math[] = {
    { .compatible = "ei, ei-vgss-gdc", },
    {}
};
MODULE_DEVICE_TABLE(of, ei_vgss_gdc_math);

static struct platform_driver gdc_pm_driver = {
    .driver = {
        .name = "ei_gdc",
        .owner = THIS_MODULE,
        .of_match_table = ei_vgss_gdc_math,
        .pm = &pm_gdc_runtime_ops,
    },
    .probe = ei_vgss_gdc_probe,
    .remove = ei_vgss_gdc_remove,
};

void ei_gdc_pm_suspend(void)
{
    //os_printk("%s:%d\n", __FUNCTION__, __LINE__);
    /* this disable the clk firstly, then close the power */
    pm_runtime_put_sync(&g_gdc_pdev->dev);
}

void ei_gdc_pm_resume(void)
{
    //os_printk("%s:%d\n", __FUNCTION__, __LINE__);
    /* this can open the power then enable the clk */
    pm_runtime_get_sync(&g_gdc_pdev->dev);
}

void lombo_gdc_pm_init(struct platform_device *pdev, void *pstGdcEx)
{
    lombo_vgss_pm_func_s pm_func;

    platform_driver_register(&gdc_pm_driver);
    pm_func.pm_suspend = ei_gdc_pm_suspend;
    pm_func.pm_resume = ei_gdc_pm_resume;
    lombo_gdc_set_pm_fun(pstGdcEx, &pm_func);
}

void lombo_gdc_pm_exit(struct platform_device *pdev)
{
    platform_driver_unregister(&gdc_pm_driver);
}
#endif

int lombo_gdc_probe(struct platform_device *pdev,
    struct ei_vgss_context *pstEiVgssCtx)
{
    struct ei_vgss_res *resCtx;
    struct device *dev = &pdev->dev;
    struct device_node *np = dev->of_node;
    struct resource *res;
    int ret = 0;
    unsigned int irq, clk_rate = 0, sclk = 0;

    /* os_printk("%s:%d\n", __FUNCTION__, __LINE__); */
    ret = lombo_vgss_get_gdc(&pstEiVgssCtx->pstGdcEx);
    if (ret) {
        os_printk("lombo_vgss_get_gdc fail\n");
        return ret;
    }
#ifdef CONFIG_PM
    lombo_gdc_pm_init(pdev, pstEiVgssCtx->pstGdcEx);
#endif

    resCtx = os_kzalloc(sizeof(struct ei_vgss_res), os_gfp_kernel);
    if (!resCtx) {
        os_printk("Kernel: alloc for resCtx fail\n");
        return -ENOMEM;
    }
    resCtx->pdev = pdev;

    /* parse register */
    resCtx->regs_res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    resCtx->reg = devm_ioremap_resource(dev, resCtx->regs_res);
    if (IS_ERR(resCtx->reg)) {
        os_printk("failed to get reg for gdc\n");
        return PTR_ERR(resCtx->reg);
    }

    resCtx->reg_size = resource_size(resCtx->regs_res);

    /* parse irq */
    res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
    if (res == NULL) {
        os_printk("get interrupt resource failed.\n");
        return -ENXIO;
    }
    irq = res->start;

    if (of_property_read_u32(np, "clk_rate", &clk_rate)) {
        os_printk("failed to of get gdc clk_rate\n");
        clk_rate = GDC_DEFAULT_CLK_RATE;
    }

    of_property_read_u32(np, "sclk", &sclk);
    if (sclk)
        g_ext_sclk_use_cnt[1]++;
    else
        g_ext_sclk_use_cnt[0]++;

    dev_set_drvdata(dev, pstEiVgssCtx->pstGdcEx);

    ret = lombo_gdc_probe_do(pstEiVgssCtx->pstVgssEx, pstEiVgssCtx->pstGdcEx,
            (void *)resCtx->regs_res, (void *)resCtx->reg, resCtx->reg_size,
            irq, clk_rate, sclk);
    if (ret) {
        os_printk("lombo_gdc_probe_do fail\n");
        return ret;
    }

    pstEiVgssCtx->pstGdcRes = resCtx;

    return ret;
}

void lombo_gdc_remove(struct ei_vgss_context *pstEiVgssCtx)
{
    struct ei_vgss_res *resCtx = (struct ei_vgss_res *)pstEiVgssCtx->pstGdcRes;
    struct device *dev = &resCtx->pdev->dev;

    lombo_gdc_remove_do(pstEiVgssCtx->pstGdcEx);
    devm_release_mem_region(dev, resCtx->regs_res->start, resCtx->reg_size);
#ifdef CONFIG_PM
    lombo_gdc_pm_exit(resCtx->pdev);
#endif
    os_kfree(resCtx);
    lombo_vgss_put_gdc(pstEiVgssCtx->pstGdcEx);
}

#ifdef CONFIG_PM
static int lombo_g2d_runtime_suspend(struct device *dev)
{
    //os_printk("%s:%d\n", __FUNCTION__, __LINE__);
    /* disable the gating of g2d */
    /* TODO(fix me): why must disable gating according this turn */
    spin_lock(&vgss_lock);
    ei_vgss_gate_disable(VGSS_MOD_G2D);
    spin_unlock(&vgss_lock);

    return 0;
}

static int lombo_g2d_runtime_resume(struct device *dev)
{
    //os_printk("%s:%d\n", __FUNCTION__, __LINE__);
    /* enable the gating of g2d */
    spin_lock(&vgss_lock);
    ei_vgss_gate_enable(VGSS_MOD_G2D);
    spin_unlock(&vgss_lock);

    return 0;
}

#ifdef CONFIG_PM_SLEEP
static int lombo_g2d_suspend(struct device *dev)
{
    //os_printk("%s:%d\n", __FUNCTION__, __LINE__);
    return 0;
}

static int lombo_g2d_resume(struct device *dev)
{
    //os_printk("%s:%d\n", __FUNCTION__, __LINE__);
    ei_vgss_reset_disable(VGSS_MOD_G2D);
    ei_vgss_reset_enable(VGSS_MOD_G2D);

    return 0;
}
#endif

static int ei_vgss_g2d_probe(struct platform_device *pdev)
{
    //os_printk("%s:%d\n", __FUNCTION__, __LINE__);
    pm_runtime_set_active(&pdev->dev);
    /* enable the pm_runtime */
    pm_runtime_enable(&pdev->dev);
    g_g2d_pdev = pdev;

    return 0;
}

static int ei_vgss_g2d_remove(struct platform_device *pdev)
{
    //os_printk("%s:%d\n", __FUNCTION__, __LINE__);
    pm_runtime_disable(&pdev->dev);

    return 0;
}

static const struct dev_pm_ops pm_g2d_runtime_ops = {
    SET_RUNTIME_PM_OPS(lombo_g2d_runtime_suspend,
        lombo_g2d_runtime_resume, NULL)
#ifdef CONFIG_PM_SLEEP
    SET_SYSTEM_SLEEP_PM_OPS(lombo_g2d_suspend,
        lombo_g2d_resume)
#endif
};

static const struct of_device_id ei_vgss_g2d_math[] = {
    { .compatible = "lombo, ei-vgss-g2d", },
    {}
};
MODULE_DEVICE_TABLE(of, ei_vgss_g2d_math);

static struct platform_driver g2d_pm_driver = {
    .driver = {
        .name = "ei_g2d",
        .owner = THIS_MODULE,
        .of_match_table = ei_vgss_g2d_math,
        .pm = &pm_g2d_runtime_ops,
    },
    .probe = ei_vgss_g2d_probe,
    .remove = ei_vgss_g2d_remove,
};

void ei_g2d_pm_suspend(void)
{
    //os_printk("%s:%d\n", __FUNCTION__, __LINE__);
    /* this disable the clk firstly, then close the power */
    pm_runtime_put_sync(&g_g2d_pdev->dev);
}

void ei_g2d_pm_resume(void)
{
    //os_printk("%s:%d\n", __FUNCTION__, __LINE__);
    /* this can open the power then enable the clk */
    pm_runtime_get_sync(&g_g2d_pdev->dev);
}

void lombo_g2d_pm_init(struct platform_device *pdev, void *pstG2dEx)
{
    lombo_vgss_pm_func_s pm_func;

    platform_driver_register(&g2d_pm_driver);
    pm_func.pm_suspend = ei_g2d_pm_suspend;
    pm_func.pm_resume = ei_g2d_pm_resume;
    lombo_g2d_set_pm_fun(pstG2dEx, &pm_func);
}

void lombo_g2d_pm_exit(struct platform_device *pdev)
{
    platform_driver_unregister(&g2d_pm_driver);
}
#endif

int lombo_g2d_probe(struct platform_device *pdev,
    struct platform_device **pCqiPdev,
    struct ei_vgss_context *pstEiVgssCtx)
{
    struct ei_vgss_res *resCtx;
    struct device *dev = &pdev->dev;
    struct device_node *np = dev->of_node;
    struct device_node *cqi;
    struct platform_device *cqi_pdev;
    struct resource *res;
    int ret = 0;
    unsigned int irq, clk_rate = 0, sclk = 0;

    /* os_printk("%s:%d\n", __FUNCTION__, __LINE__); */
    ret = lombo_vgss_get_g2d(&pstEiVgssCtx->pstG2dEx);
    if (ret) {
        os_printk("lombo_vgss_get_g2d fail\n");
        return ret;
    }
#ifdef CONFIG_PM
    lombo_g2d_pm_init(pdev, pstEiVgssCtx->pstG2dEx);
#endif

    resCtx = os_kzalloc(sizeof(struct ei_vgss_res), os_gfp_kernel);
    if (!resCtx) {
        os_printk("Kernel: alloc for resCtx fail\n");
        return -ENOMEM;
    }
    resCtx->pdev = pdev;

    /* cqi is configured as a child node of g2d by dts,
     * we use this node to decide whether use cqi or not.
     */
    cqi = of_get_child_by_name(dev->of_node, EI_DEV_CQI);
    if (cqi) {
        cqi_pdev = of_find_device_by_node(cqi);
        if (!cqi_pdev) {
            os_printk("failed to find device cqi\n");
        } else {
            *pCqiPdev = cqi_pdev;
        }
    } else {
        os_printk("failed to parse phandle of cqi\n");
    }

    /* parse register */
    resCtx->regs_res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    resCtx->reg = devm_ioremap_resource(dev, resCtx->regs_res);
    if (IS_ERR(resCtx->reg)) {
        os_printk("failed to get reg for g2d\n");
        return PTR_ERR(resCtx->reg);
    }

    resCtx->reg_size = resource_size(resCtx->regs_res);

    /* parse irq */
    res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
    if (res == NULL) {
        os_printk("get interrupt resource failed.\n");
        return -ENXIO;
    }
    irq = res->start;

    if (of_property_read_u32(np, "clk_rate", &clk_rate)) {
        os_printk("failed to of get g2d clk_rate\n");
    }

    of_property_read_u32(np, "sclk", &sclk);
    if (sclk)
        g_ext_sclk_use_cnt[1]++;
    else
        g_ext_sclk_use_cnt[0]++;

    dev_set_drvdata(dev, pstEiVgssCtx->pstG2dEx);

    ret = lombo_g2d_probe_do(pstEiVgssCtx->pstVgssEx, pstEiVgssCtx->pstG2dEx,
            (void *)resCtx->regs_res, (void *)resCtx->reg, resCtx->reg_size,
            irq, clk_rate, sclk);
    if (ret) {
        os_printk("lombo_g2d_probe_do fail\n");
        return ret;
    }

    pstEiVgssCtx->pstG2dRes = resCtx;

    return ret;
}

void lombo_g2d_remove(struct ei_vgss_context *pstEiVgssCtx)
{
    struct ei_vgss_res *resCtx = (struct ei_vgss_res *)pstEiVgssCtx->pstG2dRes;
    struct device *dev = &resCtx->pdev->dev;

    /* os_printk("%s:%d\n", __FUNCTION__, __LINE__); */
    lombo_g2d_remove_do(pstEiVgssCtx->pstG2dEx);
    devm_release_mem_region(dev, resCtx->regs_res->start, resCtx->reg_size);
#ifdef CONFIG_PM
    lombo_g2d_pm_exit(resCtx->pdev);
#endif
    os_kfree(resCtx);
    lombo_vgss_put_g2d(pstEiVgssCtx->pstG2dEx);
}

#ifdef CONFIG_PM
static int lombo_cqi_runtime_suspend(struct device *dev)
{
    //os_printk("%s:%d\n", __FUNCTION__, __LINE__);
    /* disable the gating of cqi */
    //spin_lock(&vgss_lock);
    //ei_vgss_gate_disable(VGSS_MOD_CQI);
    //ei_vgss_gate_disable(VGSS_MOD_ABT);
    //spin_unlock(&vgss_lock);

    return 0;
}

static int lombo_cqi_runtime_resume(struct device *dev)
{
    //os_printk("%s:%d\n", __FUNCTION__, __LINE__);
    /* enable the gating of cqi */
    //spin_lock(&vgss_lock);
    //ei_vgss_gate_enable(VGSS_MOD_CQI);
    //ei_vgss_gate_enable(VGSS_MOD_ABT);
    //spin_unlock(&vgss_lock);

    return 0;
}

#ifdef CONFIG_PM_SLEEP
static int lombo_cqi_suspend(struct device *dev)
{
    /* TODO */
    return 0;
}

static int lombo_cqi_resume(struct device *dev)
{
    /* TODO */
    return 0;
}

static int lombo_cqi_suspend_late(struct device *dev)
{
    /* TODO */
    return 0;
}
#endif

static int ei_vgss_cqi_probe(struct platform_device *pdev)
{
    //os_printk("%s:%d\n", __FUNCTION__, __LINE__);
    pm_runtime_set_active(&pdev->dev);
    /* enable the pm_runtime */
    pm_runtime_enable(&pdev->dev);
    g_cqi_pdev = pdev;

    return 0;
}

static int ei_vgss_cqi_remove(struct platform_device *pdev)
{
    //os_printk("%s:%d\n", __FUNCTION__, __LINE__);
    pm_runtime_disable(&pdev->dev);

    return 0;
}

static const struct dev_pm_ops pm_cqi_runtime_ops = {
    SET_RUNTIME_PM_OPS(lombo_cqi_runtime_suspend,
        lombo_cqi_runtime_resume, NULL)
#ifdef CONFIG_PM_SLEEP
    SET_SYSTEM_SLEEP_PM_OPS(lombo_cqi_suspend,
        lombo_cqi_resume)
    .suspend_late = lombo_cqi_suspend_late,
#endif
};

static const struct of_device_id ei_vgss_cqi_math[] = {
    { .compatible = "lombo, ei-vgss-cqi", },
    {}
};
MODULE_DEVICE_TABLE(of, ei_vgss_cqi_math);

static struct platform_driver cqi_pm_driver = {
    .driver = {
        .name = "ei_cqi",
        .owner = THIS_MODULE,
        .of_match_table = ei_vgss_cqi_math,
        .pm = &pm_cqi_runtime_ops,
    },
    .probe = ei_vgss_cqi_probe,
    .remove = ei_vgss_cqi_remove,
};

void ei_cqi_pm_suspend(void)
{
    //os_printk("%s:%d\n", __FUNCTION__, __LINE__);
    /* this disable the clk firstly, then close the power */
    pm_runtime_put_sync(&g_cqi_pdev->dev);
    //pm_runtime_put_sync(&g_g2d_pdev->dev);
}

void ei_cqi_pm_resume(void)
{
    //os_printk("%s:%d\n", __FUNCTION__, __LINE__);
    /* this can open the power then enable the clk */
    pm_runtime_get_sync(&g_cqi_pdev->dev);
    //pm_runtime_get_sync(&g_g2d_pdev->dev);
}

void lombo_cqi_pm_init(struct platform_device *pdev, void *pstCqiEx)
{
    lombo_vgss_pm_func_s pm_func;

    platform_driver_register(&cqi_pm_driver);
    pm_func.pm_suspend = ei_cqi_pm_suspend;
    pm_func.pm_resume = ei_cqi_pm_resume;
    lombo_cqi_set_pm_fun(pstCqiEx, &pm_func);
}

void lombo_cqi_pm_exit(struct platform_device *pdev)
{
    platform_driver_unregister(&cqi_pm_driver);
}
#endif

int lombo_g2d_cqi_probe(struct platform_device *pdev,
    struct ei_vgss_context *pstEiVgssCtx)
{
    struct ei_vgss_res *resCtx;
    struct device *dev = &pdev->dev;
    struct device_node *np = dev->of_node;
    struct resource *res;
    int ret = 0;
    unsigned int irq, clk_rate = 0, sclk = 0;

    /* os_printk("%s:%d\n", __FUNCTION__, __LINE__); */
    ret = lombo_vgss_get_cqi(&pstEiVgssCtx->pstCqiEx);
    if (ret) {
        os_printk("lombo_vgss_get_cqi fail\n");
        return ret;
    }
#ifdef CONFIG_PM
    lombo_cqi_pm_init(pdev, pstEiVgssCtx->pstCqiEx);
#endif

    resCtx = os_kzalloc(sizeof(struct ei_vgss_res), os_gfp_kernel);
    if (!resCtx) {
        os_printk("Kernel: alloc for resCtx fail\n");
        return -ENOMEM;
    }
    resCtx->pdev = pdev;

    /* parse register */
    resCtx->regs_res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    resCtx->reg = devm_ioremap_resource(dev, resCtx->regs_res);
    if (IS_ERR(resCtx->reg)) {
        os_printk("failed to get reg for cqi\n");
        ret = PTR_ERR(resCtx->reg);
        return -ENXIO;
    }

    resCtx->reg_size = resource_size(resCtx->regs_res);

    /* parse irq */
    res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
    if (res == NULL) {
        os_printk("get interrupt resource failed.\n");
        return -ENXIO;
    }
    irq = res->start;

    if (of_property_read_u32(np, "clk_rate", &clk_rate)) {
        os_printk("failed to of get cqi clk_rate\n");
    }

    of_property_read_u32(np, "sclk", &sclk);
    if (sclk)
        g_ext_sclk_use_cnt[1]++;
    else
        g_ext_sclk_use_cnt[0]++;

    dev_set_drvdata(dev, pstEiVgssCtx->pstG2dEx);

    ret = lombo_cqi_probe_do(pstEiVgssCtx->pstVgssEx,
            pstEiVgssCtx->pstG2dEx, pstEiVgssCtx->pstCqiEx,
            (void *)resCtx->regs_res, (void *)resCtx->reg, resCtx->reg_size,
            irq, clk_rate, sclk);
    if (ret) {
        os_printk("lombo_cqi_probe_do fail\n");
        return ret;
    }

    pstEiVgssCtx->pstCqiRes = resCtx;

    return 0;
}

void lombo_g2d_cqi_remove(struct ei_vgss_context *pstEiVgssCtx)
{
    struct ei_vgss_res *resCtx = (struct ei_vgss_res *)pstEiVgssCtx->pstCqiRes;
    struct device *dev = &resCtx->pdev->dev;

    /* os_printk("%s:%d\n", __FUNCTION__, __LINE__); */
    lombo_cqi_remove_do(pstEiVgssCtx->pstCqiEx);
    devm_release_mem_region(dev, resCtx->regs_res->start, resCtx->reg_size);
#ifdef CONFIG_PM
    lombo_cqi_pm_exit(resCtx->pdev);
#endif
    os_kfree(resCtx);
    lombo_vgss_put_cqi(pstEiVgssCtx->pstCqiEx);
}

void ei_vgss_get_child_dev(struct device *dev, struct ei_vgss_context *pstEiVgssCtx)
{
    struct device_node *node;
    struct platform_device *pdev;
    struct platform_device *cqi_pdev = NULL;

    for_each_available_child_of_node(dev->of_node, node) {
        pdev = of_find_device_by_node(node);
        //os_printk("node->name:%s\n", node->name);
        if (!pdev) {
            continue;
        }

        if (!strcmp(node->name, EI_DEV_ROT)) {
            lombo_rot_probe(pdev, pstEiVgssCtx);
        }

        if (!strcmp(node->name, EI_DEV_GDC)) {
            lombo_gdc_probe(pdev, pstEiVgssCtx);
        }

        if (!strcmp(node->name, EI_DEV_G2D)) {
            lombo_g2d_probe(pdev, &cqi_pdev, pstEiVgssCtx);
            if (cqi_pdev) {
                lombo_g2d_cqi_probe(cqi_pdev, pstEiVgssCtx);
            }
        }
    }

}

void ei_vgss_put_child_dev(struct ei_vgss_context *pstEiVgssCtx)
{
    if (pstEiVgssCtx->pstRotEx) {
        lombo_rot_remove(pstEiVgssCtx);
    }

    if (pstEiVgssCtx->pstGdcEx) {
        lombo_gdc_remove(pstEiVgssCtx);
    }

    if (pstEiVgssCtx->pstG2dEx) {
        if (pstEiVgssCtx->pstCqiEx) {
            lombo_g2d_cqi_remove(pstEiVgssCtx);
        }
        lombo_g2d_remove(pstEiVgssCtx);
    }
}

static int lombo_vgss_parse_sclk(struct device *dev,
    void *pstVgssEx)
{
    struct device_node *np_sclk;
    char name[16] = { 0 };
    char name_1[16] = { 0 };
    int i;
    uint32_t value;

    void *ahb_gate = NULL;
    void *ahb_reset = NULL;
    void *mem_axi_gate = NULL;
    void *ext_sclk[LOMBO_VGSS_EXT_SCLK_NUM] = {0};
    void *ext_psclk[LOMBO_VGSS_EXT_SCLK_NUM] = {0};
    struct lombo_vgss_sclk top_sclk[LOMBO_VGSS_SCLK_NUM] = {0};

    /* parse bus clks */
    /* use clk_get instead of devm_clk_get for pm */
    ahb_gate = clk_get(dev, "ahb_gate");
    if (IS_ERR(ahb_gate)) {
        os_printk("failed to get vgss ahb_gate\n");
        return -ENODEV;
    }
    ahb_reset = devm_clk_get(dev, "ahb_reset");
    if (IS_ERR(ahb_reset)) {
        os_printk("failed to get vgss ahb_reset\n");
        return -ENODEV;
    }
    mem_axi_gate = clk_get(dev, "mem_axi_gate");
    if (IS_ERR(mem_axi_gate)) {
        os_printk("failed to get vgss mem_axi_gate\n");
        return -ENODEV;
    }

#ifdef CONFIG_PM
    g_ahb_gate = ahb_gate;
    g_ahb_reset = ahb_reset;
    g_mem_axi_gate = mem_axi_gate;
#endif

    /* parse ext sclk & psclk */
    strncpy(name, "ext_sclk0", sizeof(name));
    strncpy(name_1, "ext_psclk0", sizeof(name_1));
    for (i = 0; i < LOMBO_VGSS_EXT_SCLK_NUM; i++) {
        struct clk *clk;
        struct clk *pclk = NULL;

        name[8] = i + '0';
        name_1[9] = i + '0';

        clk = clk_get(dev, name);
        if (IS_ERR(clk)) {
            continue;
        }
        ext_sclk[i] = clk;

        pclk = devm_clk_get(dev, name_1);
        if (!IS_ERR(pclk)) {
            clk_set_parent(clk, pclk);
            ext_psclk[i] = pclk;
        }
#ifdef CONFIG_PM
        g_ext_sclk[i] = clk;
#endif
        /* os_printk("has %s\n", name); */
    }

    lombo_vgss_parse_sclk_do(pstVgssEx,
        ahb_gate, ahb_reset, mem_axi_gate,
        ext_sclk, ext_psclk);

    /* create inner sclk */
    strncpy(name, "sclk0", sizeof(name));
    for (i = 0; i < LOMBO_VGSS_SCLK_NUM; i++) {
        struct lombo_vgss_sclk *sclk = &top_sclk[i];

        name[4] = '0' + i;
        np_sclk = of_get_child_by_name(dev->of_node, name);
        if (IS_ERR_OR_NULL(np_sclk) ||
            !of_device_is_available(np_sclk)) {
            continue;
        }

        if (!of_property_read_u32(np_sclk, "ext_sclk", &value)) {
            /* os_printk("%s: ext_sclk=%d\n", name, value);  */
            if (value < LOMBO_VGSS_EXT_SCLK_NUM) {
                sclk->ext_sclk = ext_sclk[value];
                sclk->ext_psclk = ext_psclk[value];
            }
        }
        if (!sclk->ext_sclk) {
            os_printk("not ext_sclk for sclk%d\n", i);
            return -EINVAL;
        }

        if (!of_property_read_u32(np_sclk, "clk_rate", &value)) {
            sclk->req_rate = value;
        }

        sclk->use_adpll = 0;
        if (!of_property_read_u32_index(np_sclk,
                "adpll_tune", 0, &value)) {
            sclk->adpll_cfg.tune0 = value;
            sclk->use_adpll++;
        }
        if (!of_property_read_u32_index(np_sclk,
                "adpll_tune", 1, &value)) {
            sclk->adpll_cfg.tune1 = value;
            sclk->use_adpll++;
        }
        sclk->use_adpll = (sclk->use_adpll == 2) ? 1 : 0;

#if 0
        os_printk("%s:req_rate=%lu, adpll=%d.",
            name, sclk->req_rate, sclk->use_adpll);
        os_printk("adpll_tune[0x%x,0x%x]\n",
            sclk->adpll_cfg.tune0, sclk->adpll_cfg.tune1);
#endif
        lombo_vgss_set_sclk(pstVgssEx, i, top_sclk[i]);
    }

    return 0;
}

int lombo_vgss_probe(struct platform_device *pdev,
    struct ei_vgss_context *pstEiVgssCtx)
{
    struct device *dev = &pdev->dev;
    struct ei_vgss_res *resCtx;
    struct resource *res;
    int ret;
    lombo_vgss_clk_func_s clk_ops;

    /* os_printk("%s:%d\n", __FUNCTION__, __LINE__); */
    resCtx = os_kzalloc(sizeof(struct ei_vgss_res), os_gfp_kernel);
    if (!resCtx) {
        os_printk("Kernel: alloc for resCtx fail\n");
        return -ENOMEM;
    }

    /* parse register */
    res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    resCtx->reg = devm_ioremap_resource(dev, res);
    if (IS_ERR(resCtx->reg)) {
        return PTR_ERR(resCtx->reg);
    }

    resCtx->regs_res = res;
    resCtx->reg_size = resource_size(resCtx->regs_res);

    clk_ops.get_rate = ei_vgss_clk_get_rate;
    clk_ops.set_rate = ei_vgss_clk_set_rate;
    clk_ops.clk_enable = ei_vgss_clk_prepare_enable;
    clk_ops.clk_disable = ei_vgss_clk_disable_unprepare;

    ret = lombo_vgss_get_top(pstEiVgssCtx->pstVgssEx);
    if (ret) {
        os_kfree(resCtx);
        os_printk("lombo_vgss_get_top fail\n");
        return ret;
    }

    ret = lombo_vgss_parse_sclk(dev, pstEiVgssCtx->pstVgssEx);
    if (ret) {
        os_printk("lombo_vgss_parse_sclk fail\n");
        return ret;
    }

    ret = lombo_vgss_probe_do(pstEiVgssCtx->pstVgssEx, &clk_ops,
            (void *)resCtx->reg, resCtx->reg_size);
    if (ret) {
        os_printk("lombo_vgss_probe_do fail\n");
        return ret;
    }

    pstEiVgssCtx->pstTopRes = resCtx;

    return 0;
}

int lombo_vgss_remove(struct platform_device *pdev, struct ei_vgss_context *pstEiVgssCtx)
{
    lombo_vgss_remove_do(pstEiVgssCtx->pstVgssEx);
    lombo_vgss_put_top(pstEiVgssCtx->pstVgssEx);
    os_kfree(pstEiVgssCtx->pstTopRes);
    return 0;
}

#ifdef CONFIG_PM
static EI_BOOL is_vgss_used_adpll(void)
{
    if (g_ext_sclk_use_cnt[1] > 1) /* 1 is for tclk */
        return EI_TRUE;

    return EI_FALSE;
}

void ei_vgss_adpll_suspend(void)
{
    if (is_vgss_used_adpll()) {
        spin_lock(&vgss_lock);
        if (g_ext_sclk1_enable_cnt > 0) {
            g_ext_sclk1_enable_cnt--;
            if (g_ext_sclk1_enable_cnt == 0)
                ei_vgss_adpll_disable();
        }
        spin_unlock(&vgss_lock);
    }
}

void ei_vgss_adpll_resume(void)
{
    if (is_vgss_used_adpll()) {
        spin_lock(&vgss_lock);
        if (g_ext_sclk1_enable_cnt == 0)
            ei_vgss_adpll_enable(g_pstVgssEx, 1);
        g_ext_sclk1_enable_cnt++;
        spin_unlock(&vgss_lock);
    }
}

void ei_vgss_pm_suspend(void)
{
    //os_printk("%s:%d\n", __FUNCTION__, __LINE__);
    /* this disable the clk firstly, then close the power */
    clk_disable(g_mem_axi_gate);
    clk_disable(g_ahb_gate);
    if (g_ext_sclk_use_cnt[0])
        clk_disable(g_ext_sclk[0]);
    if (g_ext_sclk_use_cnt[1]) {
        ei_vgss_adpll_suspend();
        clk_disable(g_ext_sclk[1]);
    }
}

void ei_vgss_pm_resume(void)
{
    //os_printk("%s:%d\n", __FUNCTION__, __LINE__);
    /* this can open the power then enable the clk */
    clk_enable(g_mem_axi_gate);
    clk_enable(g_ahb_gate);
    if (g_ext_sclk_use_cnt[0])
        clk_enable(g_ext_sclk[0]);
    if (g_ext_sclk_use_cnt[1]) {
        clk_enable(g_ext_sclk[1]);
        ei_vgss_adpll_resume();
    }
}

void lombo_vgss_pm_init(void *pstVgssEx)
{
    lombo_vgss_pm_func_s pm_func;

    pm_func.pm_suspend = ei_vgss_pm_suspend;
    pm_func.pm_resume = ei_vgss_pm_resume;
    lombo_vgss_set_pm_fun(pstVgssEx, &pm_func);
}
#endif

static int ei_vgss_probe(struct platform_device *pdev)
{
    int ret, pm = 0;
    struct device *dev = &pdev->dev;
    struct ei_vgss_context *pstEiVgssCtx;

    pstEiVgssCtx = os_kzalloc(sizeof(*pstEiVgssCtx), os_gfp_kernel);
    if (!pstEiVgssCtx) {
        os_printk("Kernel: alloc for pstEiVgssCtx fail\n");
        return -ENOMEM;
    }

    ret = ei_vgss_init_helper(&pstEiVgssCtx->pstVgssEx);
    if (ret) {
        os_printk("ei_vgss_init_helper fail\n");
        return ret;
    }

    ret = lombo_vgss_probe(pdev, pstEiVgssCtx);
    if (ret) {
        os_printk("lombo_vgss_probe fail\n");
    }

    dev_set_drvdata(dev, pstEiVgssCtx);

    ei_vgss_get_child_dev(dev, pstEiVgssCtx);

#ifdef CONFIG_PM
    pm = 1;
#endif
    g_ext_sclk_use_cnt[1]++; /* for tclk */
    os_printk("sclk_use_cnt[%d %d] vgss pm:%d\n", g_ext_sclk_use_cnt[0], g_ext_sclk_use_cnt[1], pm);

#ifdef CONFIG_PM
    /* alloc a pm_clk */
    ret = pm_clk_create(&pdev->dev);
    ret |= pm_clk_add_clk(&pdev->dev, g_ahb_gate);
    ret |= pm_clk_add_clk(&pdev->dev, g_mem_axi_gate);
    if (g_ext_sclk_use_cnt[0])
        ret |= pm_clk_add_clk(&pdev->dev, g_ext_sclk[0]);
    if (g_ext_sclk_use_cnt[1])
        ret |= pm_clk_add_clk(&pdev->dev, g_ext_sclk[1]);
    if (ret) {
        os_printk("%s:%d, initialize the pm clk error\n", __func__, __LINE__);
        return -1;
    }

    pm_runtime_set_active(&pdev->dev);
    /* enable the pm_runtime */
    pm_runtime_enable(&pdev->dev);
    /* enable the clk */
    pm_clk_resume(&pdev->dev);

    spin_lock_init(&vgss_lock);

    clk_disable(g_mem_axi_gate);
    clk_disable(g_ahb_gate);
    if (g_ext_sclk_use_cnt[0])
        clk_disable(g_ext_sclk[0]);
    if (g_ext_sclk_use_cnt[1]) {
        if (is_vgss_used_adpll())
            ei_vgss_adpll_disable();
        clk_disable(g_ext_sclk[1]);
    }
    g_pstVgssEx = pstEiVgssCtx->pstVgssEx;
    lombo_vgss_pm_init(pstEiVgssCtx->pstVgssEx);
#endif

    os_printk("ei_vgss init ok\n");

    return 0;
}

static int ei_vgss_remove(struct platform_device *pdev)
{
    struct device *dev = &pdev->dev;
    struct ei_vgss_context *pstEiVgssCtx = dev_get_drvdata(dev);

#ifdef CONFIG_PM
    clk_enable(g_mem_axi_gate);
    clk_enable(g_ahb_gate);
    if (g_ext_sclk_use_cnt[0])
        clk_enable(g_ext_sclk[0]);
    if (g_ext_sclk_use_cnt[1]) {
        clk_enable(g_ext_sclk[1]);
        if (is_vgss_used_adpll())
            ei_vgss_adpll_enable(pstEiVgssCtx->pstVgssEx, 1);
    }
#endif

    ei_vgss_put_child_dev(pstEiVgssCtx);
#ifdef CONFIG_PM
    pm_runtime_disable(&pdev->dev);
    if (!pm_runtime_status_suspended(&pdev->dev)) {
        pm_clk_suspend(&pdev->dev);
    }

    pm_clk_destroy(&pdev->dev);
#endif

    lombo_vgss_remove(pdev, pstEiVgssCtx);
    ei_vgss_exit_helper(pstEiVgssCtx->pstVgssEx);
    os_kfree(pstEiVgssCtx);

    return 0;
}

#ifdef CONFIG_PM
static int lombo_vgss_runtime_suspend(struct device *dev)
{
    //os_printk("%s:%d\n", __FUNCTION__, __LINE__);
    ei_vgss_adpll_suspend();
    //pm_clk_suspend(dev);
    return 0;
}

static int lombo_vgss_runtime_resume(struct device *dev)
{
    //os_printk("%s:%d\n", __FUNCTION__, __LINE__);
    //pm_clk_resume(dev);
    ei_vgss_adpll_resume();

    return 0;
}

static int lombo_vgss_suspend_noirq(struct device *dev)
{
    if (!pm_runtime_status_suspended(dev))
        ei_vgss_adpll_suspend();

    return 0;
}

static int lombo_vgss_resume_noirq(struct device *dev)
{
    if (!pm_runtime_status_suspended(dev))
        ei_vgss_adpll_resume();

    return 0;
}

#ifdef CONFIG_PM_SLEEP
static int lombo_vgss_suspend(struct device *dev)
{
    //os_printk("%s:%d\n", __FUNCTION__, __LINE__);
    //ei_vgss_adpll_suspend();
    //pm_clk_suspend(dev);

    return 0;
}

static int lombo_vgss_resume(struct device *dev)
{
    //os_printk("%s:%d\n", __FUNCTION__, __LINE__);
    clk_disable_unprepare(g_ahb_reset);
    clk_prepare_enable(g_ahb_reset);

#ifndef CONFIG_PM_GENERIC_DOMAINS
    /* clk_prepare_enable(ptimer_gate); */
    /* lombo_module_adpll_enable(); */
#else

    if (pm_runtime_status_suspended(dev)) {
        pm_clk_resume(dev);
        ei_vgss_adpll_resume();
    }

#if 0
    os_printk("%s:%d 0xf400a204 0x%x\n", __FUNCTION__, __LINE__, os_readl(0xf400a204));
    os_printk("%s:%d 0xf400a224 0x%x\n", __FUNCTION__, __LINE__, os_readl(0xf400a224));
    os_printk("%s:%d 0xf400a340 0x%x\n", __FUNCTION__, __LINE__, os_readl(0xf400a340));
    os_printk("%s:%d 0xf400ab10 0x%x\n", __FUNCTION__, __LINE__, os_readl(0xf400ab10));
    os_printk("%s:%d 0xf400ab14 0x%x\n", __FUNCTION__, __LINE__, os_readl(0xf400ab14));
#endif

    ei_vgss_reset_disable(VGSS_MOD_TOP);
    ei_vgss_reset_enable(VGSS_MOD_TOP);
    ei_vgss_top_and_sub_resume(g_pstVgssEx);

    if (pm_runtime_status_suspended(dev)) {
        ei_vgss_adpll_suspend();
        pm_clk_suspend(dev);
    }
#endif

    return 0;
}
#endif

static const struct dev_pm_ops vgss_pm_runtime_ops = {
    SET_RUNTIME_PM_OPS(lombo_vgss_runtime_suspend,
        lombo_vgss_runtime_resume, NULL)
#ifdef CONFIG_PM_SLEEP
    SET_SYSTEM_SLEEP_PM_OPS(lombo_vgss_suspend,
        lombo_vgss_resume)
    .suspend_noirq = lombo_vgss_suspend_noirq,
    .resume_noirq = lombo_vgss_resume_noirq,
#endif
};
#endif

static const struct of_device_id ei_vgss_match[] = {
    { .compatible = "ei, ei-vgss" },
    {},
};

MODULE_DEVICE_TABLE(of, ei_vgss_match);

static struct platform_driver ei_vgss_driver = {
    .probe          = ei_vgss_probe,
    .remove         = ei_vgss_remove,
    .driver         = {
        .name           = "ei_vgss",
        .of_match_table = ei_vgss_match,
#ifdef CONFIG_PM
        .pm = &vgss_pm_runtime_ops,
#endif
    },
};

os_module_platform_driver(ei_vgss_driver);

MODULE_LICENSE("GPL");
#endif

